#!/usr/bin/perl

use Guid;
use Tabler;
use FileHandle;
use Peekable;


my $guid_ASF_Header_Object = Guid->new("75B22630-668E-11CF-A6D9-00AA0062CE6C");
my $guid_ASF_Data_Object = Guid->new("75B22636-668E-11CF-A6D9-00AA0062CE6C");
my $guid_ASF_Simple_Index_Object = Guid->new("33000890-E5B1-11CF-89F4-00A0C90349CB");
my $guid_ASF_Index_Object = Guid->new("D6E229D3-35DA-11D1-9034-00A0C90349BE");
my $guid_ASF_Media_Object_Index_Object = Guid->new("FEB103F8-12AD-4C64-840F-2A1D2F7AD48C");
my $guid_ASF_Timecode_Index_Object = Guid->new("3CB73FD0-0C4A-4803-953D-EDF7B6228F0C");

my $guid_ASF_File_Properties_Object = Guid->new("8CABDCA1-A947-11CF-8EE4-00C00C205365");
my $guid_ASF_Stream_Properties_Object = Guid->new("B7DC0791-A9B7-11CF-8EE6-00C00C205365");
my $guid_ASF_Header_Extension_Object = Guid->new("5FBF03B5-A92E-11CF-8EE3-00C00C205365");
my $guid_ASF_Codec_List_Object = Guid->new("86D15240-311D-11D0-A3A4-00A0C90348F6");
my $guid_ASF_Script_Command_Object = Guid->new("1EFB1A30-0B62-11D0-A39B-00A0C90348F6");
my $guid_ASF_Marker_Object = Guid->new("F487CD01-A951-11CF-8EE6-00C00C205365");
my $guid_ASF_Bitrate_Mutual_Exclusion_Object = Guid->new("D6E229DC-35DA-11D1-9034-00A0C90349BE");
my $guid_ASF_Error_Correction_Object = Guid->new("75B22635-668E-11CF-A6D9-00AA0062CE6C");
my $guid_ASF_Content_Description_Object = Guid->new("75B22633-668E-11CF-A6D9-00AA0062CE6C");
my $guid_ASF_Extended_Content_Description_Object = Guid->new("D2D0A440-E307-11D2-97F0-00A0C95EA850");
my $guid_ASF_Content_Branding_Object = Guid->new("2211B3FA-BD23-11D2-B4B7-00A0C955FC6E");
my $guid_ASF_Stream_Bitrate_Properties_Object = Guid->new("7BF875CE-468D-11D1-8D82-006097C9A2B2");
my $guid_ASF_Content_Encryption_Object = Guid->new("2211B3FB-BD23-11D2-B4B7-00A0C955FC6E");
my $guid_ASF_Extended_Content_Encryption_Object = Guid->new("298AE614-2622-4C17-B935-DAE07EE9289C");
my $guid_ASF_Digital_Signature_Object = Guid->new("2211B3FC-BD23-11D2-B4B7-00A0C955FC6E");
my $guid_ASF_Padding_Object = Guid->new("1806D474-CADF-4509-A4BA-9AABCB96AAE8");

my $guid_ASF_Extended_Stream_Properties_Object = Guid->new("14E6A5CB-C672-4332-8399-A96952065B5A");
my $guid_ASF_Advanced_Mutual_Exclusion_Object = Guid->new("A08649CF-4775-4670-8A16-6E35357566CD");
my $guid_ASF_Group_Mutual_Exclusion_Object = Guid->new("D1465A40-5A79-4338-B71B-E36B8FD6C249");
my $guid_ASF_Stream_Prioritization_Object = Guid->new("D4FED15B-88D3-454F-81F0-ED5C45999E24");
my $guid_ASF_Bandwidth_Sharing_Object = Guid->new("A69609E6-517B-11D2-B6AF-00C04FD908E9");
my $guid_ASF_Language_List_Object = Guid->new("7C4346A9-EFE0-4BFC-B229-393EDE415C85");
my $guid_ASF_Metadata_Object = Guid->new("C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA");
my $guid_ASF_Metadata_Library_Object = Guid->new("44231C94-9498-49D1-A141-1D134E457054");
my $guid_ASF_Index_Parameters_Object = Guid->new("D6E229DF-35DA-11D1-9034-00A0C90349BE");
my $guid_ASF_Media_Object_Index_Parameters_Object = Guid->new("6B203BAD-3F11-48E4-ACA8-D7613DE2CFA7");
my $guid_ASF_Timecode_Index_Parameters_Object = Guid->new("F55E496D-9797-4B5D-8C8B-604DFE9BFB24");
my $guid_ASF_Compatibility_Object = Guid->new("75B22630-668E-11CF-A6D9-00AA0062CE6C");
my $guid_ASF_Compatibility_Object_2 = Guid->new("26F18B5D-4584-47EC-9F5F-0E651F0452C9");
my $guid_ASF_Advanced_Content_Encryption_Object = Guid->new("43058533-6981-49E6-9B74-AD12CB86D58C");

my $guid_ASF_Audio_Media = Guid->new("F8699E40-5B4D-11CF-A8FD-00805F5C442B");
my $guid_ASF_Video_Media = Guid->new("BC19EFC0-5B4D-11CF-A8FD-00805F5C442B");
my $guid_ASF_Command_Media = Guid->new("59DACFC0-59E6-11D0-A3AC-00A0C90348F6");
my $guid_ASF_JFIF_Media = Guid->new("B61BE100-5B4E-11CF-A8FD-00805F5C442B");
my $guid_ASF_Degradable_JPEG_Media = Guid->new("35907DE0-E415-11CF-A917-00805F5C442B");
my $guid_ASF_File_Transfer_Media = Guid->new("91BD222C-F21C-497A-8B6D-5AA86BFC0185");
my $guid_ASF_Binary_Media = Guid->new("3AFB65E2-47EF-40F2-AC2C-70A90D71D343");

my $guid_FORMAT_WaveFormatEx = Guid->new("05589F81-C356-11CE-BF01-00AA0055595A");
my $guid_MEDIASUBTYPE_MPEG2_AUDIO = Guid->new("E06D802B-DB46-11CF-B4D1-00805F6CBBEA");
my $guid_MEDIASUBTYPE_MPEG1Payload = Guid->new("E436EB81-524F-11CE-9F53-0020AF0BA770");
my $guid_MEDIASUBTYPE_DOLBY_AC3 = Guid->new("E06D802C-DB46-11CF-B4D1-00805F6CBBEA");


my $guid_NULL = Guid->new("00000000-0000-0000-0000-000000000000");

sub littleendian_to_num {
    my $v = shift;
    my $val = 0;

    while ( length($v) > 0 ) {
	$val = ord(substr($v, length($v)-1, 1)) + $val * 256;
	$v = substr($v, 0, -1);
    }
    return $val;
}

sub skip_object {
    my $fh = shift;
    my $level = shift;
    my $objname = shift;
    my $d;
    my $len;
    my $objects;
    my $locallen;
    
    $pos = $fh->tell;
    $fh->peek($d, 24) == 24 || die("too short file\n");
    $len = littleendian_to_num(substr($d, 16, 8));
    if ( $len < 24 ) {
	die "parse_asf_file: too small object, len=" . $len;
    }
    print sprintf("%012x", $pos), " ";
    print " " x (2*$level), "ASF Object (";
    print sprintf("%016x", $len), ") guid=";
    print $objname, " <";
    print sprintf("%012x", $pos+$len), ">\n";

    $fh->skip($len);
    
    @_[0] -= $len;
}

sub parse_asf_header_object {
    my $fh = shift;
    my $pos;
    my $d;
    my $len;
    my $objects;

    $pos = $fh->tell;
    $fh->peek($d, 24) == 24 || die("too short file\n");
    $len = littleendian_to_num($d);
    if ( $len < 30 ) {
	die("parse_asf_file: too small object, len=" . val($len));
    }
    print sprintf("%012x", $pos), " ASF Object (";
    print sprintf("%016x", $len), ") guid=ASF_Header_Object <";
    print sprintf("%012x", $pos+$len), ">\n";
    $fh->skip(24) == 24 || die("too short file\n");
    $len -= 24;

    $fh->peek($d, 6) == 6 || die("too short file\n");
    $objects = littleendian_to_num(substr($d, 0, 4));
    print sprintf("%012x", $pos+8), "   Number of Header Objects: ";
    print ( $objects > 0 ? $objects : " " );
    print " (", sprintf("%08x", $objects), ")\n";
    $fh->skip(6) == 6 || die("too short file\n");
    $len -= 6;

    while ( $len > 0 ) {
	if ( $fh->peek($d, 16) < 16 ) {
	    print("end of stream\n");
	    return 1;
	}
	
	$guid = Guid->new($d);	

	if ( $guid->equal($guid_ASF_File_Properties_Object) ) {
	    skip_object($fh, 1, "ASF_File_Properties_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Stream_Properties_Object) ) {
	    parse_asf_stream_properties_object($fh, 1, $len);
 	} elsif ( $guid->equal($guid_ASF_Header_Extension_Object) ) {
	    parse_asf_header_extension_object($fh, $len);
 	} elsif ( $guid->equal($guid_ASF_Codec_List_Object) ) {
	    skip_object($fh, 1, "ASF_Codec_List_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Script_Command_Object) ) {
	    skip_object($fh, 1, "ASF_Script_Command_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Marker_Object) ) {
	    skip_object($fh, 1, "ASF_Marker_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Bitrate_Mutual_Exclusion_Object) ) {
	    skip_object($fh, 1, "ASF_Bitrate_Mutual_Exclusion_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Error_Correction_Object) ) {
	    skip_object($fh, 1, "ASF_Error_Correction_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Content_Description_Object) ) {
	    skip_object($fh, 1, "ASF_Content_Description_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Extended_Content_Description_Object) ) {
	    skip_object($fh, 1, "ASF_Extended_Content_Description_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Content_Branding_Object) ) {
	    skip_object($fh, 1, "ASF_Content_Branding_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Stream_Bitrate_Properties_Object) ) {
	    skip_object($fh, 1, "ASF_Stream_Bitrate_Properties_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Content_Encryption_Object) ) {
	    skip_object($fh, 1, "ASF_Content_Encryption_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Extended_Content_Encryption_Object) ) {
	    skip_object($fh, 1, "ASF_Extended_Content_Encryption_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Digital_Signature_Object) ) {
	    skip_object($fh, 1, "ASF_Digital_Signature_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Padding_Object) ) {
	    skip_object($fh, 1, "ASF_Padding_Object", $len);
	} else {
	    skip_object($fh, 1, $guid->to_string, $len);
	}
    }
    @_[0] -= $size;
}

sub parse_asf_header_extension_object {
    my $fh = shift;
    my $pos;
    my $d;
    my $len;
    my $size;
    my $objects;

    $pos = $fh->tell;
    $fh->peek($d, 24) == 24 || die("too short file\n");
    $size = $len = littleendian_to_num(substr($d, 16, 8));
    if ( $len < 46 ) {
	die("parse_asf_header_extension_object: too small object, len=" . val($len));
    }
    print sprintf("%012x", $pos-16), "   ASF Object (";
    print sprintf("%016x", $len), ") guid=ASF_Header_Extension_Object <";
    print sprintf("%012x", $pos-16+$len), ">\n";
    $fh->skip(24) == 24 || die("too short file");
    $len -= 24;

    $fh->peek($d, 22) == 22 || die("too short file\n");
    $objects = littleendian_to_num(substr($d, 18, 4));
    print sprintf("%012x", $pos+26), "     Header Extension Data Size: ";
    print ( $objects > 0 ? $objects : " " );
    print " (", sprintf("%08x", $objects), ")\n";
    $fh->skip(22) == 22 || die("too short file");    
    $len -= 22;

    while ( $len > 0 ) {
	if ( $fh->peek($d, 16) != 16 ) {
	    print("end of stream\n");
	    return 1;
	}
	
	$guid = Guid->new($d);	

	if ( $guid->equal($guid_ASF_Extended_Stream_Properties_Object) ) {
	    parse_asf_extended_stream_properties_object($fh, $len);
 	} elsif ( $guid->equal($guid_ASF_Advanced_Mutual_Exclusion_Object) ) {
	    skip_object($fh, 2, "ASF_Advanced_Mutual_Exclusion_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Group_Mutual_Exclusion_Object) ) {
	    skip_object($fh, 2, "ASF_Group_Mutual_Exclusion_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Stream_Prioritization_Object) ) {
	    skip_object($fh, 2, "ASF_Stream_Prioritization_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Bandwidth_Sharing_Object) ) {
	    skip_object($fh, 2, "ASF_Bandwidth_Sharing_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Language_List_Object) ) {
	    skip_object($fh, 2, "ASF_Language_List_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Metadata_Object) ) {
	    skip_object($fh, 2, "ASF_Metadata_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Metadata_Library_Object) ) {
	    skip_object($fh, 2, "ASF_Metadata_Library_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Index_Parameters_Object) ) {
	    skip_object($fh, 2, "ASF_Index_Parameters_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Media_Object_Index_Parameters_Object) ) {
	    skip_object($fh, 2, "ASF_Media_Object_Index_Parameters_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Timecode_Index_Parameters_Object) ) {
	    skip_object($fh, 2, "ASF_Timecode_Index_Parameters_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Compatibility_Object) ) {
	    skip_object($fh, 2, "ASF_Compatibility_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Compatibility_Object_2) ) {
	    skip_object($fh, 2, "ASF_Compatibility_Object_2", $len);
 	} elsif ( $guid->equal($guid_ASF_Advanced_Content_Encryption_Object) ) {
	    skip_object($fh, 2, "ASF_Advanced_Content_Encryption_Object", $len);
 	} elsif ( $guid->equal($guid_ASF_Padding_Object) ) {
	    skip_object($fh, 2, "ASF_Padding_Object", $len);
	} else {
	    skip_object($fh, 2, $guid->to_string, $len);
	}

    }
    @_[0] -= $size;
}

sub parse_asf_stream_properties_object {
    my $fh = shift;
    my $level = shift;
    my $pos;
    my $d;
    my $len;
    my $size;
    my $off;
    my $val;
    my $bit;

    my $guid_stream;
    my $guid_ec;
    my $tsd_len;
    my $ecd_len;
    my $tabler = Tabler->new($level+1);
    my $tabler_flags = Tabler->new($level+2);

    $pos = $fh->tell;
    $fh->peek($d, 24) == 24 || die("too short file\n");
    $size = $len = littleendian_to_num(substr($d, 16, 8));
    if ( $len < 78 ) {
	die("parse_asf_stream_properties_object: too small object, len=" . val($len));
    }
    print sprintf("%012x", $pos);
    print sprintf(" " x ( $level * 2 + 1 )), "ASF Object (";
    print sprintf("%016x", $len), ") guid=ASF_Stream_Properties_Object <";
    print sprintf("%012x", $pos+$len), ">\n";
    $fh->skip(24) == 24 || die("too short file\n");
    $len -= 24;

    $pos = $fh->tell;;
    $fh->peek($d, 54) == 54 || die("too short file\n");
    $fh->skip(54) == 54 || die("too short file\n");
    $len -= 54;

    $guid_stream = Guid->new(substr($d, 0, 16));
    $val = $guid_stream->to_string;
    if ( $guid_stream->equal($guid_ASF_Audio_Media) ) {
	$val = "ASF_Audio_Media";
    } elsif ( $guid_stream->equal($guid_ASF_Video_Media) ) {
	$val = "ASF_Video_Media";
    } elsif ( $guid_stream->equal($guid_ASF_Command_Media) ) {
	$val = "ASF_Command_Media";
    } elsif ( $guid_stream->equal($guid_ASF_JFIF_Media) ) {
	$val = "ASF_JFIF_Media";
    } elsif ( $guid_stream->equal($guid_ASF_Degradable_JPEG_Media) ) {
	$val = "ASF_Degradable_JPEG_Media";
    } elsif ( $guid_stream->equal($guid_ASF_File_Transfer_Media) ) {
	$val = "ASF_File_Transfer_Media";
    } elsif ( $guid_stream->equal($guid_ASF_Binary_Media) ) {
	$val = "ASF_Binary_Media";
    }
    $tabler->add(sprintf("%012x", $pos), "Stream Type: ", "guid=" . $val);
    $d = substr($d, 16);
    $pos += 16;

    $guid_ec = Guid->new(substr($d, 0, 16));
    $val = $guid_ec->to_string;
    $tabler->add(sprintf("%012x", $pos), "Error Correction Type: ", "guid=" . $val);
    $d = substr($d, 16);
    $pos += 16;

    $val = littleendian_to_num(substr($d, 0, 8));
    $tabler->add(sprintf("%012x", $pos), "Time Offset: ", ($val > 0 ? $val : "") . " (" . sprintf("%016x", $val) . ")");
    $d = substr($d, 8);
    $pos += 8;

    $val = littleendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Type-Specific Data Len.: ", $val . " (" . sprintf("%08x", $val) . ")");
    $tsd_len = $val;
    $d = substr($d, 4);
    $pos += 4;

    $val = littleendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Error Correction Data Len.: ", $val . " (" . sprintf("%08x", $val) . ")");
    $ecd_len = $val;
    $d = substr($d, 4);
    $pos += 4;

    $val = littleendian_to_num(substr($d, 0, 2));
    $bit = $val & 0x7f;
    $tabler_flags->add(" " x 12, "Stream Number: ", $bit . " (" . sprintf("%02x", $bit) . ")");
    $bit = $val & 0x8000;
    $bit = 1 if $bit;
    $tabler_flags->add(" " x 12, "Encrypted Content Flag: ", $bit . " (" . sprintf("%x", $bit) . ")");
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%04x", $val) . ")", $tabler_flags);
    $d = substr($d, 6);
    $pos += 6;

    $tabler->print;

    if ( $tsd_len > 0 ) {
	
	$tabler->add(sprintf("%012x", $pos), "Type Specified Data:", "");
	$tabler->print;

	my $tabler_tsd = Tabler->new($level+2);
	my $format_len;

	if ( $guid_stream->equal($guid_ASF_Audio_Media) ) {
	    parse_struct_waveformatex($fh, $level + 2, $len);
	} elsif ( $guid_stream->equal($guid_ASF_Video_Media) ) {

	    if ( $len < 11 ) {
		die("parse_asf_stream_properties_object: too small object, len=" . val($len));
	    }
	    $fh->peek($d, 11) == 11 || die("too short file\n");
	    $fh->skip(11) == 11 || die("too short file\n");
	    $len -= 11;
	    
	    $val = littleendian_to_num(substr($d, 0, 4));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Encoded Image Width: ", $val . " (" . sprintf("%08x", $val) . ")");
	    $d = substr($d, 4);
	    $pos += 4;

	    $val = littleendian_to_num(substr($d, 0, 4));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Encoded Image Height: ", $val . " (" . sprintf("%08x", $val) . ")");
	    $d = substr($d, 5);
	    $pos += 5;

	    $val = littleendian_to_num(substr($d, 0, 2));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Format Data Size: ", $val . " (" . sprintf("%04x", $val) . ")");
	    $format_len = $val;
	    $d = substr($d, 2);
	    $pos += 2;

	    $tabler_tsd->print;

	    $tabler_tsd->add(sprintf("%012x", $pos), "Format Data:", "");
	    $tabler_tsd->print;
	    
	    $tabler_tsd->set_level($level+3);

	    if ( $format_len < 40 ) {
		die("parse_asf_stream_properties_object: too small object, len=" . val($len));
	    }
	    $fh->peek($d, 40) == 40 || die("too short file\n");
	    $fh->skip(40) == 40 || die("too short file\n");
	    $len -= 40;
	    
	    $val = littleendian_to_num(substr($d, 0, 4));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Format Data Size: ", $val . " (" . sprintf("%08x", $val) . ")");
	    $d = substr($d, 4);
	    $pos += 4;

	    $val = littleendian_to_num(substr($d, 0, 4));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Image Width: ", $val . " (" . sprintf("%08x", $val) . ")");
	    $d = substr($d, 4);
	    $pos += 4;

	    $val = littleendian_to_num(substr($d, 0, 4));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Image Height: ", $val . " (" . sprintf("%04x", $val) . ")");
	    $d = substr($d, 6);
	    $pos += 6;

	    $val = littleendian_to_num(substr($d, 0, 2));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Bits Per Pixel Count: ", $val . " (" . sprintf("%08x", $val) . ")");
	    $d = substr($d, 2);
	    $pos += 2;

	    $val = littleendian_to_num(substr($d, 0, 4));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Compressed ID: ", "'" . substr($d, 0, 4) . "' (" . sprintf("%08x", $val) . ")");
	    $d = substr($d, 4);
	    $pos += 4;

	    $val = littleendian_to_num(substr($d, 0, 4));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Image Size: ", $val . " (" . sprintf("%08x", $val) . ")");
	    $d = substr($d, 4);
	    $pos += 4;

	    $val = littleendian_to_num(substr($d, 0, 4));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Horizontal Pixels Per Meter: ", $val . " (" . sprintf("%08x", $val) . ")");
	    $d = substr($d, 4);
	    $pos += 4;

	    $val = littleendian_to_num(substr($d, 0, 4));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Vertical Pixels Per Meter: ", $val . " (" . sprintf("%08x", $val) . ")");
	    $d = substr($d, 4);
	    $pos += 4;

	    $val = littleendian_to_num(substr($d, 0, 4));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Colors Used Count: ", $val . " (" . sprintf("%08x", $val) . ")");
	    $d = substr($d, 4);
	    $pos += 4;

	    $val = littleendian_to_num(substr($d, 0, 4));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Important Colors Count: ", $val . " (" . sprintf("%08x", $val) . ")");
	    $d = substr($d, 4);
	    $pos += 4;
	    
	    $tabler_tsd->print;

	    $tabler_tsd->add(sprintf("%012x", $pos), "Codec Specific Data:", "");
	    $tabler_tsd->print;
	    
	    parse_dump($fh, $level + 4, $format_len - 40);
	    $len -= $format_len - 40;
	    $pos += $format_len - 40;

	} elsif ( $guid_stream->equal($guid_ASF_Binary_Media) ) {
	    my $guid;

	    if ( $len < 64 ) {
		die("parse_asf_stream_properties_object: too small object, len=" . val($len));
	    }
	    $fh->peek($d, 64) == 64 || die("too short file\n");
	    $fh->skip(64) == 64 || die("too short file\n");
	    $len -= 64;
	    
	    $guid = Guid->new(substr($d, 0, 16));
	    $val = $guid->to_string;
	    if ( $guid->equal($guid_ASF_Binary_Media) ) {
		$val = "ASF_Binary_Media";
	    }
	    $tabler_tsd->add(sprintf("%012x", $pos), "Major Media Type: ", "guid=" . $val);
	    $d = substr($d, 16);
	    $pos += 16;

	    $guid = Guid->new(substr($d, 0, 16));
	    $val = $guid->to_string;
	    $tabler_tsd->add(sprintf("%012x", $pos), "Media Subtype: ", "guid=" . $val);
	    $d = substr($d, 16);
	    $pos += 16;

	    $val = littleendian_to_num(substr($d, 0, 4));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Fixed Size Samples: ", $val . " (" . sprintf("%08x", $val) . ")");
	    $d = substr($d, 4);
	    $pos += 4;

	    $val = littleendian_to_num(substr($d, 0, 4));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Temporal Compression: ", $val . " (" . sprintf("%08x", $val) . ")");
	    $d = substr($d, 4);
	    $pos += 4;

	    $val = littleendian_to_num(substr($d, 0, 4));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Sample Size: ", $val . " (" . sprintf("%08x", $val) . ")");
	    $d = substr($d, 4);
	    $pos += 4;

	    $guid = Guid->new(substr($d, 0, 16));
	    $val = $guid->to_string;
	    $tabler_tsd->add(sprintf("%012x", $pos), "Format Type: ", "guid=" . $val);
	    $d = substr($d, 16);
	    $pos += 16;

	    $val = littleendian_to_num(substr($d, 0, 4));
	    $tabler_tsd->add(sprintf("%012x", $pos), "Format Data Size: ", $val . " (" . sprintf("%08x", $val) . ")");
	    $format_len = $val;
	    $d = substr($d, 4);
	    $pos += 4;


	    $tabler_tsd->print;

	    $tabler_tsd->add(sprintf("%012x", $pos), "Format Data:", "");
	    $tabler_tsd->print;
	    
	    $tabler_tsd->set_level($level+3);

	    if ( Guid->new("c4c4c4d1-0049-4e2b-98fb-9537f6ce516d")->equal($guid) ) {
		if ( $format_len < 32 ) {
		    die("parse_asf_stream_properties_object: too small object, len=" . val($len));
		}
		if ( $format_len > 32 ) {
		    $tabler_tsd->add(sprintf("%012x", $pos), "Format Data:", "");
		    $tabler_tsd->print;
	    
		    parse_dump($fh, $level + 4, $format_len - 32);
		    $len -= $format_len - 32;
		    $pos += $format_len - 32;
		}

		$fh->peek($d, 32) == 32 || die("too short file\n");
		$fh->skip(32) == 32 || die("too short file\n");
		$len -= $format_len;
	    
		$guid = Guid->new(substr($d, 0, 16));
		$val = $guid->to_string;
		if ( $guid->equal($guid_ASF_Binary_Media) ) {
		    $val = "ASF_Binary_Media";
		}
		$tabler_tsd->add(sprintf("%012x", $pos), "Media Subtype: ", "guid=" . $val);
		$d = substr($d, 16);
		$pos += 16;

		$guid = Guid->new(substr($d, 0, 16));
		$val = $guid->to_string;
		$tabler_tsd->add(sprintf("%012x", $pos), "Format Type: ", "guid=" . $val);
		$d = substr($d, 16);
		$pos += 16;
		
		$tabler_tsd->print;
	    }
	    
	}

    }

    @_[0] -= $size;
}


sub parse_dump {
    my $fh = shift;
    my $level = shift;
    my $len = shift;    
    my $size;
    my $d;
    my $i;

    while ( $len > 0 ) {
	$size = $len;
	$size = 16 if $size > 16;

	$fh->peek($d, $size) == $size || die("too short file");
	
	print sprintf("%012x", $fh->tell);
	print " " x ( 1 + $level * 2 ), "( ";
	for $i ( 0 .. $size-1 ) {
	    print sprintf("%02x ", ord(substr($d, $i, 1)));
	}
	print "   " x ( 16 - $size );
	print ")\n";

	$fh->skip($size) == $size || die("too short file");
	$len -= $size;
    }

}

sub parse_asf_extended_stream_properties_object {
    my $fh = shift;
    my $pos;
    my $d;
    my $len;
    my $size;
    my $off;
    my $val;
    my $bit;
    my $sn_count;
    my $pes_count;
    my $tabler = Tabler->new(3);
    my $tabler_flags = Tabler->new(4);

    $pos = $fh->tell;
    $fh->peek($d, 24) == 24 || die("too short file");
    $size = $len = littleendian_to_num(substr($d, 16, 8));
    if ( $len < 88 ) {
	die("parse_asf_extended_stream_properties_object: too small object, len=" . $len);
    }
    print sprintf("%012x", $pos), "     ASF Object (";
    print sprintf("%016x", $len), ") guid=ASF_Extended_Stream_Properties_Object <";
    print sprintf("%012x", $pos+$len), ">\n";
    $fh->skip(24) == 24 || die("too short file");
    $len -= 24;

    $pos = $fh->tell;
    $fh->peek($d, 64) == 64 || die("too short file");
    $fh->skip(64) == 64 || die("too short file");
    $len -= 64;

    $val = littleendian_to_num(substr($d, 0, 8));
    $tabler->add(sprintf("%012x", $pos), "Start Time: ", " (" . sprintf("%016x", $val) . ")");
    $d = substr($d, 8);
    $pos += 8;

    $val = littleendian_to_num(substr($d, 0, 8));
    $tabler->add(sprintf("%012x", $pos), "End Time: ", " (" . sprintf("%016x", $val) . ")");
    $d = substr($d, 8);
    $pos += 8;
    
    $val = littleendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Data Bitrate: ", ($val > 0 ? $val : "") . " (" . sprintf("%08x", $val) . ")");
    $d = substr($d, 4);
    $pos += 4;

    $val = littleendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Buffer Size: ", ($val > 0 ? $val : "") . " (" . sprintf("%08x", $val) . ")");
    $d = substr($d, 4);
    $pos += 4;

    $val = littleendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Ini. Buffer Fullness: ", ($val > 0 ? $val : "") . " (" . sprintf("%08x", $val) . ")");
    $d = substr($d, 4);
    $pos += 4;

    $val = littleendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Alt. Data Bitrate: ", ($val > 0 ? $val : "") . " (" . sprintf("%08x", $val) . ")");
    $d = substr($d, 4);
    $pos += 4;

    $val = littleendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Alt. Buffer Size: ", ($val > 0 ? $val : "") . " (" . sprintf("%08x", $val) . ")");
    $d = substr($d, 4);
    $pos += 4;

    $val = littleendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Alt. Ini. Buffer Fullness: ", ($val > 0 ? $val : "") . " (" . sprintf("%08x", $val) . ")");
    $d = substr($d, 4);
    $pos += 4;

    $val = littleendian_to_num(substr($d, 0, 4));
    $tabler->add(sprintf("%012x", $pos), "Max. Object Size: ", ($val > 0 ? $val : "") . " (" . sprintf("%08x", $val) . ")");
    $d = substr($d, 4);
    $pos += 4;

    $val = littleendian_to_num(substr($d, 0, 4));
    $bit = $val & 0x01;
    $bit = 1 if $bit;
    $tabler_flags->add(" " x 12, "Reliable Flag: ", $bit . " (" . sprintf("%x", $bit) . ")");
    $bit = $val & 0x02;
    $bit = 1 if $bit;
    $tabler_flags->add(" " x 12, "Seekable Flag: ", $bit . " (" . sprintf("%x", $bit) . ")");
    $bit = $val & 0x04;
    $bit = 1 if $bit;
    $tabler_flags->add(" " x 12, "No Cleanpoints Flag: ", $bit . " (" . sprintf("%x", $bit) . ")");
    $bit = $val & 0x08;
    $bit = 1 if $bit;
    $tabler_flags->add(" " x 12, "Resend Live Cleanpoints Flag: ", $bit . " (" . sprintf("%x", $bit) . ")");
    $tabler->add(sprintf("%012x", $pos), "Flags: ", " (" . sprintf("%08x", $val) . ")", $tabler_flags);
    $d = substr($d, 4);
    $pos += 4;

    $val = littleendian_to_num(substr($d, 0, 2));
    $tabler->add(sprintf("%012x", $pos), "Stream Number: ", ($val > 0 ? $val : "") . " (" . sprintf("%04x", $val) . ")");
    $d = substr($d, 2);
    $pos += 2;

    $val = littleendian_to_num(substr($d, 0, 2));
    $tabler->add(sprintf("%012x", $pos), "Stream Language ID Index: ", $val . " (" . sprintf("%04x", $val) . ")");
    $d = substr($d, 2);
    $pos += 2;

    $val = littleendian_to_num(substr($d, 0, 8));
    $tabler->add(sprintf("%012x", $pos), "Average Time Per Frame: ", ($val > 0 ? $val : "") . " (" . sprintf("%04x", $val) . ")");
    $d = substr($d, 8);
    $pos += 8;

    $val = littleendian_to_num(substr($d, 0, 2));
    $tabler->add(sprintf("%012x", $pos), "Stream Name Count: ", $val . " (" . sprintf("%04x", $val) . ")");
    $sn_count = $val;
    $d = substr($d, 2);
    $pos += 2;

    $val = littleendian_to_num(substr($d, 0, 2));
    $tabler->add(sprintf("%012x", $pos), "Payload Ext. System Count: ", $val . " (" . sprintf("%04x", $val) . ")");
    $pes_count = $val;
    $d = substr($d, 2);
    $pos += 2;

    for my $sn_num ( 0 .. $sn_count-1 ) {
	if ( $len < 4 ) {
	    die("parse_asf_extended_stream_properties_object: too small object, len=" . val($len));
	}

	my $tabler_sn = Tabler->new;
	my $sn_pos = $pos;
	my $sn_len;

	$fh->peek($d, 4) || die("too short file $!\n");
	$len -= 4;

	$val = littleendian_to_num(substr($d, 0, 2));
	$tabler_sn->add(sprintf("%012x", $pos), "Language ID Index: ", $val . " (" . sprintf("%04x", $val) . ")");
	$d = substr($d, 2);
	$pos += 2;

	$val = littleendian_to_num(substr($d, 0, 2));
	$tabler_sn->add(sprintf("%012x", $pos), "Stream Name Length: ", $val . " (" . sprintf("%04x", $val) . ")");
	$sn_len = $val;
	$d = substr($d, 2);
	$pos += 2;

	$tabler->add(sprintf("%012x", $sn_pos), "Stream Names[" . $sn_num . "]: ", "", $tabler_sn);
	seek($fh, $sn_len, 1);
	$len -= $sn_len;
	$pos += $sn_len;
    }

    for my $pes_num ( 0 .. $pes_count-1 ) {
	if ( $len < 22 ) {
	    die("parse_asf_extended_stream_properties_object: too small object, len=" . $len);
	}
	
	my $tabler_pes = Tabler->new;
	my $pes_pos = $pos;
	my $pes_len;
	my $pes_guid;

	$fh->peek($d, 22) == 22 || die("too short file");
	$fh->skip(22) == 22 || die("too short file");
	$len -= 22;

	$pes_guid = Guid->new(substr($d, 0, 16));
	$tabler_pes->add(sprintf("%012x", $pos), "ID: ", "guid=" . $pes_guid->to_string);
	$d = substr($d, 16);
	$pos += 16;

	$val = littleendian_to_num(substr($d, 0, 2));
	$tabler_pes->add(sprintf("%012x", $pos), "Data Size: ", ( $val == 0xffff ? "variable" : ( $val >= 0 && $val < 256 ? $val : "" )) . " (" . sprintf("%04x", $val) . ")");
	$d = substr($d, 2);
	$pos += 2;

	
	$val = littleendian_to_num(substr($d, 0, 4));
	$tabler_pes->add(sprintf("%012x", $pos), "Info. Len.: ", $val . " (" . sprintf("%08x", $val) . ")");
	$pes_len = $val;
	$d = substr($d, 4);
	$pos += 4;
	
	$tabler->add(sprintf("%012x", $pes_pos), "Payload Ext. System[" . $pes_num . "]: ", "", $tabler_pes);
	$fh->skip($pes_len) == $pes_len || die("too short file");
	$len -= $pes_len;
	$pos += $pes_len;
    }

    $tabler->print;

    if ( $len > 16 ) {
	if ( $fh->peek($d, 16) < 16 ) {
	    print("end of stream\n");
	    return 1;
	}
	$len -= 16;	
	$guid = Guid->new($d);	

	if ( $guid->equal($guid_ASF_Stream_Properties_Object) ) {
	    parse_asf_stream_properties_object($fh, 3, $len);
	} else {
	    skip_object($fh, 3, $guid->to_string, $len);
	}

    }
    @_[0] -= $size;
}



sub parse_asf_file {
    my $fh = shift;
    my $d = "";
    my $guid;
    my $len = 0;

    while ( 1 ) {
	if ( $fh->peek($d, 16) != 16 ) {
	    print("end of stream\n");
	    return 1;
	}
	
	$guid = Guid->new($d);
	if ( $guid->equal($guid_ASF_Header_Object) ) { parse_asf_header_object($fh, substr($d, 16)); }
	else {
	    skip_object($fh, 0, $guid->to_string, $len);
	}
	$d = "";
    }
}


{
    my $fho = new FileHandle;
    my $fh = new Peekable;
    
  ONEFILE: for (@ARGV) {
      print "----\n";
      print $_, "\n\n";
      
      open($fho, $_) || (print("cannot be open.\n"), next ONEFILE);

      print tell($fho);
      $fh->set_fd($fho);
      
      $fh->peek($d, 16) || die("too short file\n");
      
      my $guid = Guid->new($d);
      
      if ( !$guid->equal($guid_ASF_Header_Object) ) {
	  print "NOT ASF FILE!!\n"; 
      } else {
	  parse_asf_file($fh);
      }
      
      
      $fh->close;
  }
}
